package naga.x.resource.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

public class HttpUtils {

	protected static final Logger LOG = LoggerFactory.getLogger(HttpUtils.class);
	protected static final int DEFAULT_CONNECT_TIME_OUT = 10000;
	protected static final int DEFAULT_READ_TIME_OUT = 20000;
	
	public static final String GET = "GET";
	public static final String POST = "POST";
	public static final String PUT = "PUT";
	public static final String DELETE = "DELETE";

	protected static ThreadLocal<Boolean> responseHoldMark = new ThreadLocal<Boolean>();
	protected static ThreadLocal<Response> responsesHolder = new ThreadLocal<Response>();
	public static class Response {
		protected int code;
		protected Map<String, List<String>> headers;
		protected byte[] data;
		public Response(int code, Map<String, List<String>> headers, byte[] data) {
			this.code = code;
			this.headers = headers;
			this.data = data;
		}
		public int getCode() {
			return code;
		}
		public Map<String, List<String>> getHeaders() {
			return headers;
		}
		public String getHeader(String name) {
			List<String> vals = headers.get(name);
			return vals != null && vals.size() > 0 ? vals.get(0) : null;
		}
//		@JSONField(serialize = false)
		public byte[] getData() {
			return data;
		}
//		@JSONField(serialize = false)
		public String getDataAsString() {
			if (data == null || data.length == 0) {
				return null;
			}
			try {
				return new String(data, IOUtils.UTF8);
			} catch (UnsupportedEncodingException e) {
				return new String(data);
			}
		}
		@Override
		public String toString() {
//			return JSON.toJSONString(this);
			return getDataAsString();
		}
	}
	
	private HttpUtils() {}
	
	public static String get(String url) {
		return get(url, (String[]) null);
	}
	
	public static String get(String url, String[] params) {
		return get(url, null, params);
	}
	
	public static String get(String url, Map<String, String> params) {
		String queryString = wrapQueryString(params);
		if (queryString != null) {
			url += '?' + queryString;
		}
		byte[] data = request(url, GET, null, null);
		if (data == null || data.length == 0) {
			return null;
		}
		try {
			return new String(data, IOUtils.UTF8);
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}
	
	public static String get(String url, String[] headers, String[] params) {
		try {
			byte[] data = getAsBytes(url, headers, params);
			if (data == null || data.length == 0) {
				return null;
			}
			return new String(data, IOUtils.UTF8);
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}
	
	public static byte[] getAsBytes(String url, String[] params) {
		return getAsBytes(url, null, params);
	}
	
	public static byte[] getAsBytes(String url, String[] headers, String[] params) {
		String queryString = wrapQueryString(params);
		if (queryString != null) {
			url += '?' + queryString;
		}
		return request(url, GET, headers, null);
	}
	
	public static String post(String url) {
		return post(url, new byte[0]);
	}
	
	public static String post(String url, byte[] params) {
		return post(url, null, params);
	}
	
	public static String post(String url, String[] headers, byte[] params) {
		try {
			byte[] data = postAsBytes(url, headers, params);
			if (data == null || data.length == 0) {
				return null;
			}
			return new String(data, IOUtils.UTF8);
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}
	
	public static String post(String url, String[] params) {
		return post(url, null, params);
	}
	
	public static String post(String url, String[] headers, String[] params) {
		try {
			byte[] data = postAsBytes(url, headers, params);
			return data != null && data.length > 0 ? new String(data, IOUtils.UTF8) : null;
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}
	
	public static String post(String url, Map<String, String> params) {
		return post(url, null, params);
	}
	
	public static String post(String url, String[] headers, Map<String, String> params) {
		String queryString = wrapQueryString(params);
		byte[] queryBytes = null;
		if (queryString != null) {
			queryBytes = IOUtils.getStringBytes(queryString, IOUtils.UTF8);
		}
		byte[] b = postAsBytes(url, headers, queryBytes);
		try {
			return b != null && b.length > 0 ? new String(b, IOUtils.UTF8) : null;
		} catch (Exception e) {
			return null;
		}
	}
	
	public static byte[] postAsBytes(String url, String[] params) {
		return postAsBytes(url, null, params);
	}
	
	public static byte[] postAsBytes(String url, String[] headers, String[] params) {
		String queryString = wrapQueryString(params);
		byte[] paramsBytes = null;
		if (queryString != null) {
			paramsBytes = IOUtils.getStringBytes(queryString, IOUtils.UTF8);
		}
		return postAsBytes(url, headers, paramsBytes);
	}
	
	public static byte[] postAsBytes(String url, byte[] params) {
		return postAsBytes(url, null, params);
	}
	
	public static byte[] postAsBytes(String url, String[] headers, byte[] params) {
		return request(url, POST, headers, params);
	}
	
	public static byte[] request(String url, String method, String[] headers, byte[] params) {
		HttpURLConnection conn = null;
		OutputStream out = null;
		InputStream in = null;
		try {
			conn = (HttpURLConnection) new URL(url).openConnection();
			conn.setConnectTimeout(DEFAULT_CONNECT_TIME_OUT);
			conn.setReadTimeout(DEFAULT_READ_TIME_OUT);
			conn.setRequestMethod(method);
			conn.setInstanceFollowRedirects(false); // don't follow the redirects
			int len;
			// set request headers
			if (headers != null && (len = headers.length) > 0) {
				for (int i = 0; i < len; i += 2) {
					conn.setRequestProperty(headers[i], headers[i + 1]);
				}
			}
//			if (StringUtils.isEmpty(conn.getRequestProperty("Content-Type"))) {
//				conn.setRequestProperty("Content-Type", "application/plain");
//			application/x-www-form-urlencoded
//			}
			if (params != null && params.length > 0) {
				conn.setDoOutput(true);
				out = conn.getOutputStream();
				out.write(params);
				IOUtils.flushQuietly(out);
			} else {
				conn.connect();
			}
//			if (App.getInstance().isDebugMode()) {
//				String p = params != null && params.length > 0 ? StringUtils.bytesToString(params, IOUtils.UTF8) : null;
//				if (p != null) {
//					url += '?' + p;
//				}
//				LOG.info("[HTTP]METHOD[{}]URL[{}]", method, url);
//			}
			try {
				in = conn.getInputStream();
			} catch (Exception e) {
				IOUtils.closeQuietly(in);
				in = conn.getErrorStream();
			}
			if (in == null) {
				return null;
			}
			byte[] buf = new byte[1024];
			len = 0;
			ByteArrayOutputStream baos = new ByteArrayOutputStream(10240);
			while ((len = in.read(buf)) != -1) {
				baos.write(buf, 0, len);
			}
			byte[] data = baos.toByteArray();
			Boolean hold = responseHoldMark.get();
			if (hold != null && hold) {
				responseHoldMark.remove();
				responsesHolder.set(new Response(conn.getResponseCode(), conn.getHeaderFields(), data));
			}
			return data;
		} catch (Exception e) {
			LOG.error("[HTTP ERROR]" + url, e);
		} finally {
			IOUtils.closeQuietly(out);
			IOUtils.closeQuietly(in);
			if (conn != null)
				conn.disconnect();
		}
		return null;
	}
	
	public static String wrapQueryString(String[] query) {
		int len;
		if (query == null || (len = query.length) == 0) {
			return null;
		}
		StringBuilder buf = new StringBuilder(128);
		for (int i = 0; i < len; i += 2) {
			buf.append(query[i]).append('=').append(query[i + 1]).append('&');
		}
		buf.setLength(buf.length() - 1); // delete the last '&'
		return buf.toString();
	}
	
	public static String wrapQueryString(Map<String, String> query) {
		if (query == null || query.isEmpty()) {
			return null;
		}
		StringBuilder buf = new StringBuilder(128);
		for (Entry<String, String> e : query.entrySet()) {
			buf.append(e.getKey()).append('=').append(e.getValue()).append('&');
		}
		buf.setLength(buf.length() - 1); // delete the last '&'
		return buf.toString();
	}
	
	public static String getRequestAddr() {
		return getRequestAddr(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest());
	}
	
	public static String getRequestAddr(HttpServletRequest req) {
		if (req == null) {
			return null;
		}
		String addr = req.getHeader("QVia");
		if (!StringUtils.isEmpty(addr) && !"unknown".equalsIgnoreCase(addr)) {
			try {
				addr = addr.substring(0, 8);
				StringBuilder buf = new StringBuilder();
				buf.append(Integer.parseInt(addr.substring(0, 2), 16)).append('.');
				buf.append(Integer.parseInt(addr.substring(2, 4), 16)).append('.');
				buf.append(Integer.parseInt(addr.substring(4, 6), 16)).append('.');
				buf.append(Integer.parseInt(addr.substring(6, 8), 16));
				return buf.toString();
			} catch (Exception e) {}
		}
		addr = req.getHeader("x-forwarded-for");
		if (!StringUtils.isEmpty(addr) && !"unknown".equalsIgnoreCase(addr)) {
			return addr;
		}
		addr = req.getHeader("Proxy-Client-IP");
		if (!StringUtils.isEmpty(addr) && !"unknown".equalsIgnoreCase(addr)) {
			return addr;
		}
		addr = req.getHeader("WL-Proxy-Client-IP");
		if (!StringUtils.isEmpty(addr) && !"unknown".equalsIgnoreCase(addr)) {
			return addr;
		}
		return req.getRemoteAddr();
	}
	
	public static void destory() {
		responsesHolder.remove();
	}
	
	public static Response getResponse() {
		Response res = responsesHolder.get();
		responsesHolder.remove();
		return res;
	}
	
	public static void holdResponse(boolean hold) {
		if (hold) {
			responseHoldMark.set(hold);
		}
	}
	
	public static void sendRedirect(HttpServletResponse res, String url) {
		try {
			res.sendRedirect(url);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void writeResponse(HttpServletResponse res, String html) {
		res.setContentType("text/html; charset=utf-8");
		res.setCharacterEncoding(IOUtils.UTF8);
		PrintWriter pw = null;
		try {
			pw = res.getWriter();
			pw.println(html);
			IOUtils.flushQuietly(pw);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(pw);
		}
	}
	
}
